
package net.wujingchao.ohos.view;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorGroup;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.components.LayoutScatter;
import ohos.agp.components.ListContainer;
import ohos.agp.components.ScrollView;
import ohos.agp.components.VelocityDetector;
import ohos.agp.render.Canvas;
import ohos.agp.render.render3d.BuildConfig;
import ohos.app.Context;
import ohos.multimodalinput.event.TouchEvent;

import java.util.ArrayList;

/**
 * Multi card menu
 */
@SuppressWarnings("unused")
public class MultiCardMenu extends ComponentContainer implements Component.TouchEventListener,
        Component.LayoutRefreshedListener, Component.DrawTask {
    /**
     * * The constant TAG
     */
    public static final String TAG = "MultiCardMenu";

    /**
     * * The constant DEBUG
     */
    private static final boolean DEBUG = BuildConfig.DEBUG;

    /**
     * * The constant DEFAULT_CARD_MARGIN_TOP
     */
    private static final int DEFAULT_CARD_MARGIN_TOP = 0;

    /**
     * * The constant DEFAULT_TITLE_BAR_HEIGHT_NO_DISPLAY
     */
    private static final int DEFAULT_TITLE_BAR_HEIGHT_NO_DISPLAY = 60;

    /**
     * * The constant DEFAULT_TITLE_BAR_HEIGHT_DISPLAY
     */
    private static final int DEFAULT_TITLE_BAR_HEIGHT_DISPLAY = 20;

    /**
     * * The constant DEFAULT_MOVE_DISTANCE_TO_TRIGGER
     */
    private static final int DEFAULT_MOVE_DISTANCE_TO_TRIGGER = 30;

    /**
     * * The constant DEFAULT_DURATION
     */
    private static final int DEFAULT_DURATION = 250;

    /**
     * * The constant MAX_CLICK_TIME
     */
    private static final int MAX_CLICK_TIME = 300;
    /**
     * * The constant CARD_MENU_BOUNDARY
     */
    private static final String CARD_MENU_BOUNDARY = "boundary";
    /**
     * * The constant CARD_MENU_FADE
     */
    private static final String CARD_MENU_FADE = "fade";
    /**
     * * The constant CARD_MENU_MARGIN_TOP
     */
    private static final String CARD_MENU_MARGIN_TOP = "margin_top";
    /**
     * * The constant CARD_MENU_TITLE_BAR_HEIGHT_DISPLAY
     */
    private static final String CARD_MENU_TITLE_BAR_HEIGHT_DISPLAY = "title_bar_height_display";
    /**
     * * The constant CARD_MENU_TITLE_BAR_HEIGHT_NO_DISPLAY
     */
    private static final String CARD_MENU_TITLE_BAR_HEIGHT_NO_DISPLAY = "title_bar_height_no_display";
    /**
     * * The constant CARD_MENU_MOVE_DISTANCE_TO_TRIGGER
     */
    private static final String CARD_MENU_MOVE_DISTANCE_TO_TRIGGER = "move_distance_to_trigger";
    /**
     * * The constant CARD_MENU_ANIMATOR_DURATION
     */
    private static final String CARD_MENU_ANIMATOR_DURATION = "animator_duration";
    /**
     * * The constant MAX_CLICK_DISTANCE
     */
    private static float MAX_CLICK_DISTANCE = 5;
    /**
     * M density
     */
    private float mDensity;
    /**
     * M title bar height no display
     */
    private float mTitleBarHeightNoDisplay;
    /**
     * M title bar height display
     */
    private float mTitleBarHeightDisplay;
    /**
     * M velocity tracker
     */
    private VelocityDetector mVelocityTracker;
    /**
     * M margin top
     */
    private float mMarginTop;
    /**
     * Delta y
     */
    private float deltaY;
    /**
     * Which card on touch
     */
    private int whichCardOnTouch = -1;
    /**
     * Down y
     */
    private float downY;
    /**
     * First down y
     */
    private float firstDownY;
    /**
     * First down x
     */
    private float firstDownX;
    /**
     * Is touch on card
     */
    private boolean isTouchOnCard = false;
    /**
     * M child count
     */
    private int mChildCount;
    /**
     * Is displaying
     */
    private boolean isDisplaying = false;
    /**
     * M open animator interpolator
     */
    private static int OPEN_ANIMATOR_INTERPOLATOR = Animator.CurveType.ACCELERATE;
    /**
     * M close animator interpolator
     */
    private static int  CLOSE_ANIMATOR_INTERPOLATOR =  Animator.CurveType.ACCELERATE;
    /**
     * M move distance to trigger
     */
    private float mMoveDistanceToTrigger;
    /**
     * M displaying card
     */
    private int mDisplayingCard = -1;
    /**
     * M max velocity
     */
    private int mMaxVelocity;
    /**
     * M min velocity
     */
    private int mMinVelocity;
    /**
     * Is dragging
     */
    private boolean isDragging = false;
    /**
     * X velocity
     */
    private float xVelocity;
    /**
     * Y velocity
     */
    private float yVelocity;
    /**
     * M on display or hide listener
     */
    private OnDisplayOrHideListener mOnDisplayOrHideListener;
    /**
     * M duration
     */
    private int mDuration;
    /**
     * Is animating
     */
    private boolean isAnimating = false;
    /**
     * M dark frame layout
     */
    private DarkFrameLayout mDarkFrameLayout;
    /**
     * Is fade
     */
    private boolean isFade;
    /**
     * M touch slop
     */
    private int mTouchSlop;
    /**
     * M press start time
     */
    private long mPressStartTime;
    /**
     * M boundary
     */
    private boolean mBoundary;
    /**
     * M touching view origin y
     */
    private float mTouchingViewOriginY;
    /**
     * M context
     */
    private Context mContext;
    /**
     * M background rid
     */
    private int mBackgroundRid = -1;
    private Animator.StateChangedListener stateChangedListener = new Animator.StateChangedListener() {
        @Override
        public void onStart(Animator animator) {
            isAnimating = true;
        }

        @Override
        public void onStop(Animator animator) {
        }

        @Override
        public void onCancel(Animator animator) {
            isAnimating = false;
        }

        @Override
        public void onEnd(Animator animator) {
            isAnimating = false;
            if (isFade) {
                mDarkFrameLayout.fade(DarkFrameLayout.MAX_ALPHA);
            }
        }

        @Override
        public void onPause(Animator animator) {
        }

        @Override
        public void onResume(Animator animator) {
        }
    };
    private Animator.StateChangedListener hideStateChangedListener = new Animator.StateChangedListener() {
        @Override
        public void onStart(Animator animator) {
            isAnimating = true;
        }

        @Override
        public void onStop(Animator animator) {
        }

        @Override
        public void onCancel(Animator animator) {
            isAnimating = false;
            isDisplaying = false;
        }

        @Override
        public void onEnd(Animator animator) {
            isAnimating = false;
            isDisplaying = false;
            if (isFade) {
                mDarkFrameLayout.fade(0);
            }
        }

        @Override
        public void onPause(Animator animator) {
        }

        @Override
        public void onResume(Animator animator) {
        }
    };

    /**
     * Multi card menu
     *
     * @param context context
     */
    public MultiCardMenu(Context context) {
        this(context, null);
    }

    /**
     * Multi card menu
     *
     * @param context context
     * @param attrs   attrs
     */
    public MultiCardMenu(Context context, AttrSet attrs) {
        this(context, attrs, null);
    }

    /**
     * Multi card menu
     *
     * @param context      context
     * @param attrs        attrs
     * @param defStyleAttr def style attr
     */
    public MultiCardMenu(Context context, AttrSet attrs, String defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        // 获得允许执行一个fling手势动作的最大速度值
        mMaxVelocity = Integer.MAX_VALUE;

        // 获得允许执行一个fling手势动作的最小速度值
        mMinVelocity = 0;

        // 获得能够进行手势滑动的距离
        mTouchSlop = 20;
        mDensity = mContext.getResourceManager().getDeviceCapability().screenDensity / 160;
        MAX_CLICK_DISTANCE = mTitleBarHeightNoDisplay = AttrUtils.getDimensionFromAttr(attrs, CARD_MENU_TITLE_BAR_HEIGHT_NO_DISPLAY, dip2px(DEFAULT_TITLE_BAR_HEIGHT_NO_DISPLAY));
        mTitleBarHeightDisplay = AttrUtils.getDimensionFromAttr(attrs, CARD_MENU_TITLE_BAR_HEIGHT_DISPLAY, dip2px(DEFAULT_TITLE_BAR_HEIGHT_DISPLAY));
        mMoveDistanceToTrigger = AttrUtils.getDimensionFromAttr(attrs, CARD_MENU_MOVE_DISTANCE_TO_TRIGGER, dip2px(DEFAULT_MOVE_DISTANCE_TO_TRIGGER));
        mMarginTop = AttrUtils.getDimensionFromAttr(attrs, CARD_MENU_MARGIN_TOP, dip2px(DEFAULT_CARD_MARGIN_TOP));
        isFade = AttrUtils.getBooleanFromAttr(attrs, CARD_MENU_FADE, true);
        mBoundary = AttrUtils.getBooleanFromAttr(attrs, CARD_MENU_BOUNDARY, false);
        mDuration = AttrUtils.getIntFromAttr(attrs, CARD_MENU_ANIMATOR_DURATION, DEFAULT_DURATION);
        setTouchEventListener(this);
        setLayoutRefreshedListener(this);
        LogUtil.info(TAG, "initMultiCardMenu");
    }

    /**
     * Set background rid *
     *
     * @param mBackgroundRid m background rid
     */
    public void setBackgroundRid(int mBackgroundRid) {
        this.mBackgroundRid = mBackgroundRid;
        LogUtil.info(TAG, "setmBackgroundRid");
        initBackgroundView();
    }

    /**
     * Init background view
     */
    private void initBackgroundView() {
        if (mBackgroundRid == -1) { // transparent background
            mBackgroundRid = ResourceTable.Layout_layout;
        }
        mDarkFrameLayout = new DarkFrameLayout(mContext);
        mDarkFrameLayout.setWidth(ComponentContainer.LayoutConfig.MATCH_PARENT);
        mDarkFrameLayout.setHeight(ComponentContainer.LayoutConfig.MATCH_PARENT);
        Component parse = LayoutScatter.getInstance(mContext).parse(mBackgroundRid, null, false);
        parse.setWidth(ComponentContainer.LayoutConfig.MATCH_PARENT);
        parse.setHeight(ComponentContainer.LayoutConfig.MATCH_PARENT);
        mDarkFrameLayout.addComponent(parse);
        mDarkFrameLayout.setMultiCardMenu(this);
        addComponent(mDarkFrameLayout, 0);
        addDrawTask(this);
    }

    /**
     * On draw *
     *
     * @param component component
     * @param canvas    canvas
     */
    @Override
    public void onDraw(Component component, Canvas canvas) {
        int height = component.getHeight();
        LogUtil.info(TAG, "onDraw" + height);
    }

    /**
     * On refreshed *
     *
     * @param component component
     */
    @Override
    public void onRefreshed(Component component) {
        if (isDisplaying) {
            return;
        }
        LogUtil.info(TAG, "onRefreshed");
        mChildCount = getChildCount();
        Component backgroundView = getComponentAt(0); // background view
        backgroundView.setContentPositionX(0);
        backgroundView.setContentPositionY(0);
        int estimatedHeight = getHeight();
        for (int i = 1; i < mChildCount; i++) {
            Component childView = getComponentAt(i);
            int contentPositionY = (int) (estimatedHeight - (mChildCount - i) * mTitleBarHeightNoDisplay);
            childView.setContentPositionX(0);
            childView.setContentPositionY(contentPositionY);
        }
    }

    /**
     * On touch event boolean
     *
     * @param component component
     * @param event     event
     * @return the boolean
     */
    @Override
    public boolean onTouchEvent(Component component, TouchEvent event) {
        LogUtil.info(TAG, " event.getAction(): " + event.getAction()
                + " getPointerCount: " + event.getPointerCount()
                + " getX: " + event.getPointerScreenPosition(0).getX()
                + " getY: " + event.getPointerScreenPosition(0).getY()
                + " getX: " + event.getPointerPosition(0).getX()
                + " getX: " + event.getPointerPosition(0).getY()
        );

        initVelocityTracker(event);
        boolean isDispatch = false;
        switch (event.getAction()) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                isDispatch = handleActionDown(event);
                break;
            case TouchEvent.POINT_MOVE:
                handleActionMove(event);
                break;
            case TouchEvent.CANCEL:
            case TouchEvent.PRIMARY_POINT_UP:
                handleActionUp(event);
                releaseVelocityTracker();
                break;
        }
        return isDispatch;
    }

    /**
     * Handle action down boolean
     *
     * @param event event
     * @return the boolean
     */
    private boolean handleActionDown(TouchEvent event) {
        boolean isConsume = false;
        mPressStartTime = System.currentTimeMillis();
        firstDownX = event.getPointerPosition(0).getX();
        firstDownY = downY = event.getPointerPosition(0).getY();
        int realChildCount = mChildCount - 1; // do not contain background view
        // Judge which card on touching
        int height = getHeight();
        float pointy = height - mChildCount * mTitleBarHeightNoDisplay;

        if (!isDisplaying && downY > pointy) {
            for (int i = 1; i <= mChildCount; i++) {
                float distance = height - mChildCount * mTitleBarHeightNoDisplay + mTitleBarHeightNoDisplay * i;
                if (downY < distance) {
                    whichCardOnTouch = i - 1;
                    isTouchOnCard = true;
                    if (mOnDisplayOrHideListener != null) {
                        mOnDisplayOrHideListener.onTouchCard(whichCardOnTouch - 1);
                    }
                    isConsume = true;
                    break;
                }
            }
            mTouchingViewOriginY = getComponentAt(whichCardOnTouch).getContentPositionY();
        } else if (isDisplaying && downY > height - (realChildCount - 1) * mTitleBarHeightDisplay) {
            hideCard(mDisplayingCard);
        } else if (isDisplaying && downY > mMarginTop && mDisplayingCard >= 0 && downY < getComponentAt(mDisplayingCard).getHeight() + mMarginTop) {
            whichCardOnTouch = mDisplayingCard;
            isTouchOnCard = true;
            if (DEBUG) {
                LogUtil.info(TAG, "whichCardOnTouch:" + whichCardOnTouch);
            }
        } else if (isDisplaying && (downY < mMarginTop || (mDisplayingCard >= 0 && (downY > mMarginTop + getComponentAt(mDisplayingCard).getHeight())))) {
            hideCard(mDisplayingCard);
        }

        if (whichCardOnTouch == 0) {
            isTouchOnCard = false;
        }
        return isConsume;
    }

    /**
     * Handle action move *
     *
     * @param event event
     */
    private void handleActionMove(TouchEvent event) {
        if (whichCardOnTouch == -1 || !isTouchOnCard) {
            return;
        }

        if (mDisplayingCard != -1 && canScrollInView((int) (firstDownY - event.getPointerPosition(0).getY()))) {
            if (DEBUG) {
                LogUtil.info(TAG, "canScrollInView:" + true);
            }
            return;
        }

        computeVelocity();
        if (Math.abs(yVelocity) < Math.abs(xVelocity)) {
            return;
        }

        if (!isDragging && Math.abs(event.getPointerPosition(0).getY() - firstDownY) > mTouchSlop
                && Math.abs(event.getPointerPosition(0).getX() - firstDownX) < mTouchSlop) {
            isDragging = true;
            downY = event.getPointerPosition(0).getY();
        }
        LogUtil.info(TAG, " isDragging: " + isDragging);
        if (isDragging) {
            deltaY = event.getPointerPosition(0).getY() - downY;
            downY = event.getPointerPosition(0).getY();
            Component touchingChildView = getComponentAt(whichCardOnTouch);
            float contentPositionY = touchingChildView.getContentPositionY();
            if (!mBoundary) {
                touchingChildView.setContentPositionY(contentPositionY + deltaY);
            } else {
                float touchingViewY = touchingChildView.getContentPositionY();
                if (touchingViewY + deltaY <= mMarginTop) {
                    touchingChildView.setContentPositionY(mMarginTop);
                } else if (touchingViewY + deltaY >= mTouchingViewOriginY) {
                    touchingChildView.setContentPositionY(mTouchingViewOriginY);
                } else {
                    touchingChildView.setContentPositionY(contentPositionY + deltaY);
                }
            }
        }

    }

    /**
     * Handle action up *
     *
     * @param event event
     */
    private void handleActionUp(TouchEvent event) {
        if (whichCardOnTouch == -1 || !isTouchOnCard) {
            return;
        }

        long pressDuration = System.currentTimeMillis() - mPressStartTime;
        computeVelocity();
        if (!isDisplaying && ((event.getPointerPosition(0).getY() - firstDownY < 0 && (Math.abs(event.getPointerPosition(0).getY() - firstDownY) > mMoveDistanceToTrigger))
                || (yVelocity < 0 && Math.abs(yVelocity) > mMinVelocity && Math.abs(yVelocity) > Math.abs(xVelocity))
        )) {
            displayCard(whichCardOnTouch);
        } else if (!isDisplaying && pressDuration < MAX_CLICK_TIME && // means click
                distance(firstDownX, firstDownY, event.getPointerPosition(0).getX(), event.getPointerPosition(0).getX()) < MAX_CLICK_DISTANCE) {
            displayCard(whichCardOnTouch);
        } else if (!isDisplaying && isDragging && ((event.getPointerPosition(0).getY() - firstDownY > 0) || Math.abs(event.getPointerPosition(0).getY() - firstDownY) < mMoveDistanceToTrigger)) {
            hideCard(whichCardOnTouch);
        } else if (isDisplaying) {
            float currentY = getComponentAt(mDisplayingCard).getContentPositionY();
            if (currentY < mMarginTop || currentY < (mMarginTop + mMoveDistanceToTrigger)) {
                float contentPositionY = getComponentAt(mDisplayingCard).getContentPositionY();
                AnimatorValue animatorValue = new AnimatorValue();
                animatorValue.setDuration(mDuration);
                animatorValue.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
                    @Override
                    public void onUpdate(AnimatorValue animatorValue, float value) {
                        getComponentAt(mDisplayingCard).setContentPositionY(contentPositionY + value * (mMarginTop - currentY));
                    }
                });
                animatorValue.start();
            } else if (currentY > (mMarginTop + mMoveDistanceToTrigger)) {
                hideCard(mDisplayingCard);
            }
        }
        isTouchOnCard = false;
        deltaY = 0;
        isDragging = false;
    }

    /**
     * Can scroll in view boolean
     *
     * @param direction direction
     * @return the boolean
     */
    private boolean canScrollInView(int direction) {
        Component view = getComponentAt(whichCardOnTouch);
        if (view instanceof ComponentContainer) {
            Component childView = findTopChildUnder((ComponentContainer) view, firstDownX, firstDownY - mMarginTop);
            if (childView == null) {
                return false;
            }

            if (childView instanceof ListContainer) {
                return absListViewCanScrollList((ListContainer) childView, direction);
            } else if (childView instanceof ScrollView) {
                return scrollViewCanScrollVertically((ScrollView) childView, direction);
            }
        }
        return false;
    }

    /**
     * Abs list view can scroll list boolean
     *
     * @param absListView abs list view
     * @param direction   direction
     * @return the boolean
     */
    private boolean absListViewCanScrollList(ListContainer absListView, int direction) {
        final int childCount = absListView.getChildCount();
        LogUtil.info(TAG, "absListViewCanScrollList childCount: " + childCount);
        if (childCount == 0) {
            return false;
        }
        final int firstPosition = absListView.getFirstVisibleItemPosition();
        if (direction > 0) {    // can scroll down
            final int lastBottom = absListView.getComponentAt(childCount - 1).getBottom();
            final int lastPosition = firstPosition + childCount;
            return lastPosition < absListView.getItemProvider().getCount() || lastBottom > absListView.getHeight() - absListView.getPaddingTop();
        } else {    // can scroll  up
            final int firstTop = absListView.getComponentAt(0).getTop();
            return firstPosition > 0 || firstTop < absListView.getPaddingTop();
        }
    }

    /**
     * Scroll view can scroll vertically boolean
     *
     * @param scrollView scroll view
     * @param direction  direction
     * @return the boolean
     */
    private boolean scrollViewCanScrollVertically(ScrollView scrollView, int direction) {
        int scrollValue = scrollView.getScrollValue(AXIS_Y);

        final int offset = Math.max(0, scrollView.getScrollValue(AXIS_Y));
        final int range = computeVerticalScrollRange(scrollView) - scrollView.getHeight();
        LogUtil.info(TAG, "scrollValue: " + scrollValue + " range: " + range + " direction: " + direction);
        if (range == 0) {
            return false;
        }
        if (direction < 0) {    // scroll up
            return offset > 0;
        } else {    // scroll down
            return offset < range - 1;
        }
    }

    /**
     * Compute vertical scroll range int
     *
     * @param scrollView scroll view
     * @return the int
     */
    private int computeVerticalScrollRange(ScrollView scrollView) {
        final int count = scrollView.getChildCount();
        final int contentHeight = scrollView.getHeight() - scrollView.getPaddingBottom() - scrollView.getPaddingTop();
        if (count == 0) {
            return contentHeight;
        }

        int scrollRange = scrollView.getComponentAt(0).getBottom();
        final int scrollY = scrollView.getScrollValue(AXIS_Y);
        final int overScrollBottom = Math.max(0, scrollRange - contentHeight);
        LogUtil.info(TAG, "computeVerticalScrollRange: count: " + count
                + "contentHeight: " + contentHeight
                + " scrollRange: " + scrollRange
                + " scrollY: " + scrollY
                + " overScrollBottom: " + overScrollBottom);
        if (scrollY < 0) {
            scrollRange -= scrollY;
        } else if (scrollY > overScrollBottom) {
            scrollRange += scrollY - overScrollBottom;
        }

        return scrollRange;
    }

    /**
     * Compute velocity
     */
    private void computeVelocity() {
        mVelocityTracker.calculateCurrentVelocity(1000, mMaxVelocity, mMaxVelocity);
        yVelocity = mVelocityTracker.getVerticalVelocity();
        xVelocity = mVelocityTracker.getHorizontalVelocity();
    }

    /**
     * Show *
     *
     * @param index index
     */
    public void show(int index) {
        if (index >= mChildCount) {
            throw new IllegalArgumentException("Card Index Not Exist");
        }
        if (index + 1 == mDisplayingCard || isDisplaying) {
            return;
        }
        displayCard(index + 1);
    }

    /**
     * Hide *
     *
     * @param index index
     */
    public void hide(int index) {
        if (index >= mChildCount) {
            throw new IllegalArgumentException("Card Index Not Exist");
        }
        if (index + 1 != mDisplayingCard || !isDisplaying) {
            return;
        }
        hideCard(index + 1);
    }

    /**
     * Set on display or hide listener *
     *
     * @param onDisplayOrHideListener on display or hide listener
     */
    public void setOnDisplayOrHideListener(OnDisplayOrHideListener onDisplayOrHideListener) {
        this.mOnDisplayOrHideListener = onDisplayOrHideListener;
    }

    /**
     * Set animator interpolator *
     *
     * @param interpolator interpolator
     */
    public void setAnimatorInterpolator(int interpolator) {
        OPEN_ANIMATOR_INTERPOLATOR = interpolator;
        CLOSE_ANIMATOR_INTERPOLATOR = interpolator;
    }

    /**
     * Get displaying card int
     *
     * @return the int
     */
    public int getDisplayingCard() {
        return mDisplayingCard - 1;
    }

    /**
     * Is displaying boolean
     *
     * @return the boolean
     */
    public boolean isDisplaying() {
        return isDisplaying;
    }

    /**
     * Is boundary boolean
     *
     * @return the boolean
     */
    public boolean isBoundary() {
        return mBoundary;
    }

    /**
     * Set boundary *
     *
     * @param boundary boundary
     */
    public void setBoundary(boolean boundary) {
        this.mBoundary = boundary;
    }

    /**
     * Is fade boolean
     *
     * @return the boolean
     */
    public boolean isFade() {
        return isFade;
    }

    /**
     * Set fade *
     *
     * @param isFade is fade
     */
    public void setFade(boolean isFade) {
        this.isFade = isFade;
    }

    /**
     * Get margin top int
     *
     * @return the int
     */
    public int getMarginTop() {
        return px2dip(mMarginTop);
    }

    /**
     * Set margin top *
     *
     * @param marginTop margin top
     */
    public void setMarginTop(int marginTop) {
        this.mMarginTop = dip2px(marginTop);
    }

    /**
     * Get title bar height no display int
     *
     * @return the int
     */
    public int getTitleBarHeightNoDisplay() {
        return px2dip(mTitleBarHeightNoDisplay);
    }

    /**
     * Set title bar height no display *
     *
     * @param titleBarHeightNoDisplay title bar height no display
     */
    public void setTitleBarHeightNoDisplay(int titleBarHeightNoDisplay) {
        this.mTitleBarHeightNoDisplay = dip2px(titleBarHeightNoDisplay);
        postLayout();
    }

    /**
     * Get title bar height display int
     *
     * @return the int
     */
    public int getTitleBarHeightDisplay() {
        return px2dip(mTitleBarHeightDisplay);
    }

    /**
     * Set title bar height display *
     *
     * @param titleBarHeightDisplay title bar height display
     */
    public void setTitleBarHeightDisplay(int titleBarHeightDisplay) {
        this.mTitleBarHeightDisplay = titleBarHeightDisplay;
        postLayout();
    }

    /**
     * Get move distance to trigger int
     *
     * @return the int
     */
    public int getMoveDistanceToTrigger() {
        return px2dip(mMoveDistanceToTrigger);
    }

    /**
     * Set move distance to trigger *
     *
     * @param moveDistanceToTrigger move distance to trigger
     */
    public void setMoveDistanceToTrigger(int moveDistanceToTrigger) {
        this.mMoveDistanceToTrigger = moveDistanceToTrigger;
    }

    /**
     * Get open animator interpolator interpolator
     *
     * @return the interpolator
     */
    public int getOpenAnimatorInterpolator() {
        return OPEN_ANIMATOR_INTERPOLATOR;
    }

    /**
     * Set open animator interpolator *
     *
     * @param interpolator interpolator
     */
    public void setOpenAnimatorInterpolator(int interpolator) {
        OPEN_ANIMATOR_INTERPOLATOR = interpolator;
    }

    /**
     * Get close animator interpolator interpolator
     *
     * @return the interpolator
     */
    public int getCloseAnimatorInterpolator() {
        return CLOSE_ANIMATOR_INTERPOLATOR;
    }

    /**
     * Set close animator interpolator *
     *
     * @param interpolator interpolator
     */
    public void setCloseAnimatorInterpolator(int interpolator) {
        CLOSE_ANIMATOR_INTERPOLATOR = interpolator;
    }

    /**
     * Get animator duration int
     *
     * @return the int
     */
    public int getAnimatorDuration() {
        return this.mDuration;
    }

    /**
     * Set animator duration *
     *
     * @param duration duration
     */
    public void setAnimatorDuration(int duration) {
        this.mDuration = duration;
    }

    /**
     * Init velocity tracker *
     *
     * @param event event
     */
    private void initVelocityTracker(TouchEvent event) {
        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityDetector.obtainInstance();
        }
        mVelocityTracker.addEvent(event);
    }

    /**
     * Release velocity tracker
     */
    private void releaseVelocityTracker() {
        if (mVelocityTracker != null) {
            mVelocityTracker.clear();
            mVelocityTracker.clear();
            mVelocityTracker = null;
        }
    }

    /**
     * Distance double
     *
     * @param x1 x 1
     * @param y1 y 1
     * @param x2 x 2
     * @param y2 y 2
     * @return the double
     */
    private double distance(float x1, float y1, float x2, float y2) {
        float deltaX1 = x2 - x1;
        float deltaY1 = y2 - y1;
        return Math.sqrt(deltaX1 * deltaX1 + deltaY * deltaY);
    }

    /**
     * Display card *
     *
     * @param which which
     */
    private void displayCard(int which) {
        if (isDisplaying || isAnimating) {
            return;
        }

        final Component displayingCard = getComponentAt(which);
        if (isFade && mDarkFrameLayout != null) {
            mDarkFrameLayout.fade(true);
        }

        startAnimator(which, displayingCard);
        isDisplaying = true;
        mDisplayingCard = which;
        if (mOnDisplayOrHideListener != null) {
            mOnDisplayOrHideListener.onDisplay(which - 1);
        }

    }

    private void startAnimator(int which, Component displayingCard) {
        ArrayList<Animator> animators = new ArrayList<>(mChildCount);
        final float distance = displayingCard.getContentPositionY() - mMarginTop;
        AnimatorValue displayAnimator = new AnimatorValue();
        displayAnimator.setDuration(mDuration);
        displayAnimator.setCurveType(OPEN_ANIMATOR_INTERPOLATOR);
        float displayingCardMovingDistance = mMarginTop - displayingCard.getContentPositionY();
        float displayingCardPositionY = displayingCard.getContentPositionY();
        displayAnimator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float value) {
                float valueY = displayingCardPositionY + (value * displayingCardMovingDistance);
                displayingCard.setContentPositionY(valueY);
                if (mDarkFrameLayout != null && isFade) {
                    mDarkFrameLayout.fade((int) ((1 - (valueY - mMarginTop) / distance) * DarkFrameLayout.MAX_ALPHA));
                }
            }
        });
        animators.add(displayAnimator);
        int num = mChildCount - 1;
        AnimatorGroup animatorGroup = new AnimatorGroup();
        AnimatorGroup.Builder animatorGroupBuilder = animatorGroup.build();
        for (int i = 1, j = 1; i < mChildCount; i++) {
            if (i != which) {
                AnimatorValue animatorValue = new AnimatorValue();
                animatorValue.setDuration(mDuration);
                animatorValue.setCurveType(OPEN_ANIMATOR_INTERPOLATOR);
                final int finalI = i;
                final float otherCardPositionY = getComponentAt(finalI).getContentPositionY();
                final float otherCardMovingDistance = getHeight() - mTitleBarHeightDisplay * (num - j) - otherCardPositionY;
                animatorValue.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
                    @Override
                    public void onUpdate(AnimatorValue animatorValue, float value) {
                        getComponentAt(finalI).setContentPositionY(otherCardPositionY + value * otherCardMovingDistance);
                    }
                });
                animators.add(animatorValue);
                j++;
            }
        }

        animatorGroup.setStateChangedListener(stateChangedListener);
        Animator[] animatorArray = animators.toArray(new Animator[animators.size()]);
        animatorGroup.runParallel(animatorArray);
        animatorGroup.start();
    }

    /**
     * Hide card *
     *
     * @param which which
     */
    private void hideCard(int which) {
        if (isAnimating) {
            return;
        }
        ArrayList<Animator> animators = new ArrayList<>(mChildCount);
        final Component displayingCard = getComponentAt(which);
        int distance = (int) (getHeight() - (mChildCount - which) * mTitleBarHeightNoDisplay);
        final int finalT = distance;
        AnimatorGroup animatorGroup = new AnimatorGroup();
        initAnimatorValue(animators, displayingCard, finalT);
        for (int i = 1; i < mChildCount; i++) {
            if (i != which) {
                distance = (int) (getHeight() - (mChildCount - i) * mTitleBarHeightNoDisplay);
                AnimatorValue animatorValue1 = new AnimatorValue();
                animatorValue1.setDuration(mDuration);
                animatorValue1.setCurveType(CLOSE_ANIMATOR_INTERPOLATOR);
                int finalT1 = distance;
                int finalI = i;
                float otherCardPositionY = getComponentAt(finalI).getContentPositionY();
                final float otherCardMovingDistance = finalT1 - otherCardPositionY;
                animatorValue1.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
                    @Override
                    public void onUpdate(AnimatorValue animatorValue, float value) {
                        float valueY = otherCardPositionY + value * otherCardMovingDistance;
                        getComponentAt(finalI).setContentPositionY(valueY);
                        if (mDarkFrameLayout != null && isFade && valueY < finalT) {
                            mDarkFrameLayout.fade((int) ((1 - valueY / finalT) * DarkFrameLayout.MAX_ALPHA));
                        }
                    }
                });
                animators.add(animatorValue1);
            }
        }
        animatorGroup.setStateChangedListener(hideStateChangedListener);
        Animator[] animatorArray = animators.toArray(new Animator[animators.size()]);
        animatorGroup.runParallel(animatorArray);
        animatorGroup.start();

        mDisplayingCard = -1;
        if (mOnDisplayOrHideListener != null) {
            mOnDisplayOrHideListener.onHide(which - 1);
        }
    }

    private void initAnimatorValue(ArrayList<Animator> animators, Component displayingCard, int finalT) {
        AnimatorValue animatorValue = new AnimatorValue();
        animatorValue.setDuration(mDuration);
        animatorValue.setCurveType(CLOSE_ANIMATOR_INTERPOLATOR);
        float displayingCardPositionY = displayingCard.getContentPositionY();
        final float displayingCardMovingDistance = finalT - displayingCardPositionY;
        animatorValue.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float value) {
                float valueY = value * displayingCardMovingDistance + displayingCardPositionY;
                displayingCard.setContentPositionY(valueY);
                if (mDarkFrameLayout != null && isFade && valueY < finalT) {
                    mDarkFrameLayout.fade((int) ((1 - valueY / finalT) * DarkFrameLayout.MAX_ALPHA));
                }
            }
        });
        animators.add(animatorValue);
    }

    /**
     * Find top child under component
     *
     * @param parentView parent view
     * @param touchX          touchX
     * @param touchY          touchY
     * @return the component
     */
    private Component findTopChildUnder(ComponentContainer parentView, float touchX, float touchY) {
        final int childCount = parentView.getChildCount();
        for (int i = childCount - 1; i >= 0; i--) {
            final Component child = parentView.getComponentAt(i);
            if (DEBUG) {
                LogUtil.info(TAG, "findTopChildUnder:"
                        + " touchX： " + touchX
                        + " touchY: " + touchY
                        + " child.getLeft(): " + child.getLeft()
                        + " child.getRight(): " + child.getRight()
                        + " child.getTop(): " + child.getTop()
                        + " child.getBottom() " + child.getBottom()
                        + " getContentPositionY: " + child.getContentPositionY());
            }
            if (touchX >= child.getLeft() && touchX < child.getRight() && touchY >= child.getTop() && touchY < child.getBottom()) {
                return child;
            }
        }
        return null;
    }

    /**
     * Px 2 dip int
     *
     * @param pxVal px val
     * @return the int
     */
    private int px2dip(float pxVal) {
        return (int) (pxVal / mDensity + 0.5f);
    }

    /**
     * Dip 2 px int
     *
     * @param dipVal dip val
     * @return the int
     */
    private int dip2px(int dipVal) {
        return (int) (dipVal * mDensity + 0.5f);
    }


    /**
     * On display or hide listener
     */
    public interface OnDisplayOrHideListener {
        /**
         * On display *
         *
         * @param which which
         */
        void onDisplay(int which);

        /**
         * On hide *
         *
         * @param which which
         */
        void onHide(int which);

        /**
         * On touch card *
         *
         * @param which which
         */
        void onTouchCard(int which);
    }
}

